/**
 *
 */
package idao.engine;

import idao.Configurable;
import idao.DaoRuntimeException;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.Date;

/**
 * Utility class for internal using.
 * 
 * @author huhao
 */
public final class DaoUtils
{

    /**
     * Create an new instance of given type. Initialize the new instance
     * if it is {@link Configurable} and given init parameters are not null.
     * 
     * @param <T> class type
     * @param type type of the instance
     * @param initParameters init parameters, maybe be null
     * @return new instance
     */
    public static <T> T newInstance(Class<T> type, String[] initParameters)
    {
        try {
            T t = type.newInstance();
            if ((initParameters != null) && Configurable.class.isAssignableFrom(type)) {
                Configurable.class.cast(t).setConfiguration(initParameters);
            }
            return t;
        } catch (InstantiationException e) {
            throw new DaoRuntimeException("Failed to create new instance of [" + type + "].", e);
        } catch (IllegalAccessException e) {
            throw new DaoRuntimeException("Failed to create new instance of [" + type + "].", e);
        }
    }

    /**
     * The following types are the so called simple type.
     * <ul>
     * java.lang.String<br>
     * java.lang.Boolean<br>
     * java.lang.Byte<br>
     * java.lang.Short<br>
     * java.lang.Integer<br>
     * java.lang.Long<br>
     * java.lang.Float<br>
     * java.lang.Double<br>
     * java.sql.Time<br>
     * java.sql.Timestamp<br>
     * java.sql.Date<br>
     * java.util.Date<br>
     * java.math.BigInteger<br>
     * java.math.BigDecimal<br>
     * java.net.URL<br>
     * byte[].class<br>
     * </ul>
     * 
     * @param type a class type
     * @return true for simple type, flase otherwise.
     */
    public static boolean isSimpleType(Class<?> type)
    {
        return (String.class.equals(type) || Integer.class.equals(type) || Byte.class.equals(type)
                        || Short.class.equals(type) || Boolean.class.equals(type)
                        || Long.class.equals(type) || Float.class.equals(type)
                        || Double.class.equals(type) || Time.class.equals(type)
                        || Timestamp.class.equals(type) || java.sql.Date.class.equals(type)
                        || java.util.Date.class.equals(type) || BigInteger.class.equals(type)
                        || BigDecimal.class.equals(type) || byte[].class.equals(type) || URL.class
                        .equals(type));
    }

    /**
     * Get value of the given ResultSet's given column per the given type.
     * Will try to get the most propriate value.
     * 
     * @param rs ResultSet to get value from
     * @param column column index, start from 1
     * @param type target type
     * @return value of the column
     * @throws SQLException in case of errors
     */
    public static Object getValue(ResultSet rs, int column, Class<?> type) throws SQLException
    {
        if (String.class.equals(type)) {
            return rs.getString(column);
        }
        if (int.class.equals(type) || Integer.class.equals(type)) {
            return rs.wasNull() ? null : rs.getInt(column);
        }
        if (byte.class.equals(type) || Byte.class.equals(type)) {
            return rs.wasNull() ? null : rs.getByte(column);
        }
        if (short.class.equals(type) || Short.class.equals(type)) {
            return rs.wasNull() ? null : rs.getShort(column);
        }
        if (long.class.equals(type) || Long.class.equals(type)) {
            return rs.wasNull() ? null : rs.getLong(column);
        }
        if (float.class.equals(type) || Float.class.equals(type)) {
            return rs.wasNull() ? null : rs.getFloat(column);
        }
        if (double.class.equals(type) || Double.class.equals(type)) {
            return rs.wasNull() ? null : rs.getDouble(column);
        }
        if (boolean.class.equals(type) || Boolean.class.equals(type)) {
            return rs.wasNull() ? null : rs.getBoolean(column);
        }
        if (byte[].class.equals(type)) {
            return rs.getBytes(column);
        }
        if (Time.class.equals(type)) {
            return rs.getTime(column);
        }
        if (java.sql.Date.class.equals(type)) {
            return rs.getDate(column);
        }
        if (Timestamp.class.equals(type) || java.util.Date.class.equals(type)) {
            return rs.getTimestamp(column);
        }
        if (BigDecimal.class.equals(type)) {
            return rs.getBigDecimal(column);
        }
        if (BigInteger.class.equals(type)) {
            return rs.getBigDecimal(column).toBigInteger();
        }
        if (URL.class.equals(type)) {
            return rs.getURL(column);
        }
        return rs.getObject(column);
    }

    /**
     * Set parameter value to given prepared statement.
     * Try to use the most appropriate setXXX method according to the type of value.
     * 
     * @param ps target prepared statement
     * @param index parameter index, start from 1
     * @param value parameter value
     * @throws SQLException if any errors occur while setting value
     */
    public static void setValue(PreparedStatement ps, int index, Object value) throws SQLException
    {
        if (value == null) {
            ps.setNull(index, Types.NULL);
        } else if (value instanceof CharSequence) {
            ps.setString(index, ((CharSequence)value).toString());
        } else if (value instanceof Integer) {
            ps.setInt(index, (Integer)value);
        } else if (value instanceof Long) {
            ps.setLong(index, (Long)value);
        } else if (value instanceof java.util.Date) {
            if (value instanceof Time) {
                ps.setTime(index, (Time)value);
            } else if (value instanceof java.sql.Date) {
                ps.setDate(index, (java.sql.Date)value);
            } else if (value instanceof Timestamp) {
                ps.setTimestamp(index, (Timestamp)value);
            } else {
                java.util.Date date = (Date)value;
                ps.setTimestamp(index, new Timestamp(date.getTime()));
            }
        } else if (value instanceof Calendar) {
            Calendar calendar = (Calendar)value;
            ps.setTimestamp(index, new Timestamp(calendar.getTimeInMillis()), calendar);
        } else if (value instanceof Boolean) {
            ps.setBoolean(index, (Boolean)value);
        } else if (value instanceof Byte) {
            ps.setByte(index, (Byte)value);
        } else if (value instanceof Short) {
            ps.setShort(index, (Short)value);
        } else if (value instanceof Float) {
            ps.setFloat(index, (Float)value);
        } else if (value instanceof Double) {
            ps.setDouble(index, (Double)value);
        } else if (value instanceof BigInteger) {
            ps.setBigDecimal(index, new BigDecimal((BigInteger)value));
        } else if (value instanceof BigDecimal) {
            ps.setBigDecimal(index, (BigDecimal)value);
        } else if (value instanceof byte[]) {
            ps.setBytes(index, (byte[])value);
        } else if (value instanceof URL) {
            ps.setURL(index, (URL)value);
        } else {
            ps.setObject(index, value);
        }
    }

    /**
     * Close given connection quietly. Exceptions will be ignored.
     * 
     * @param c statement to be closed
     */
    public static void close(Connection c)
    {
        if (c != null) {
            try {
                c.close();
            } catch (Exception e) {
                // ignored
            }
        }
    }

    /**
     * Close given statement quietly. Exceptions will be ignored.
     * 
     * @param stmt statement to be closed
     */
    public static void close(Statement stmt)
    {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (Exception e) {
                // ignored
            }
        }
    }

    /**
     * Close given result set quietly. Exceptions will be ignored.
     * 
     * @param rs result set to be closed
     */
    public static void close(ResultSet rs)
    {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                // ignored
            }
        }
    }

    private DaoUtils()
    {
        // No instantiation needed.
    }
}
